//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using OpenLS.Core.Internals;

namespace OpenLS.Core
{
    using UndoUnitStack = ObservableStack<IUndoUnit>;

    public class UndoManager
    {
        /* internal UndoUnitScope CreateUndoScope(string name)
        {
            if (!IsEnabled)
                return null;
            return new UndoUnitScope(new CompositeUndoUnit(name), this);
        }*/

        private readonly UndoUnitStack _redoStack = new UndoUnitStack();
        private readonly UndoUnitStack _undoStack = new UndoUnitStack();
        private bool _isEnabled;

        public UndoManager()
        {
            IsEnabled = true;
        }

        public bool IsEnabled
        {
            get { return _isEnabled; }
            set
            {
                if (_isEnabled == value)
                    return;

                _isEnabled = value;
                if (!value)
                {
                    _undoStack.Clear();
                    _redoStack.Clear();
                }
            }
        }

        public UndoUnitStack UndoStack
        {
            get { return _undoStack; }
        }

        public UndoUnitStack RedoStack
        {
            get { return _redoStack; }
        }

        /// <summary>
        /// Get the state of the current undo manager
        /// </summary>
        public UndoState State { get; private set; }

        public IUndoUnit LastUnit { get; set; }
        internal ICompositeUndoUnit OpenedUnit { get; set; }

        internal ICompositeUndoUnit DeepestOpenUnit
        {
            get
            {
                ICompositeUndoUnit result = OpenedUnit;
                if (result != null)
                {
                    while (result.OpenedUnit != null)
                        result = result.OpenedUnit;
                }
                return result;
            }
        }

        public UndoUnitScope CreateUndoScope(string text)
        {
            if (!IsEnabled)
                return null;
            return new UndoUnitScope(new CompositeUndoUnit(text), this);
        }

        public void Open(ICompositeUndoUnit unit)
        {
            if (!IsEnabled)
            {
                throw new InvalidOperationException();
            }
            if (unit == null)
            {
                throw new ArgumentNullException("unit");
            }
            ICompositeUndoUnit deepestUnit = DeepestOpenUnit;
            if (deepestUnit == unit)
            {
                throw new InvalidOperationException();
            }
            if (deepestUnit == null)
            {
                if (unit != LastUnit)
                {
                    Add(unit);
                    LastUnit = (unit);
                }
                OpenedUnit = unit;
                unit.Parent = this;
            }
            else
            {
                unit.Parent = deepestUnit;
                deepestUnit.Open(unit);
            }
        }


        public void Add(IUndoUnit unit)
        {
            ICompositeUndoUnit compositeUnit = DeepestOpenUnit;
            if (compositeUnit != null)
            {
                compositeUnit.Add(unit);
                return;
            }
            if (!(unit is ICompositeUndoUnit))
                throw new InvalidOperationException();

            // SJE: Added on 11-07
            try
            {
                compositeUnit = (ICompositeUndoUnit)unit;
            }
            catch (InvalidOperationException)
            {
                // Undo operation unavilable for this object state.
                return;
            }
            
            compositeUnit.Parent = this;
            switch (State)
            {
                case UndoState.Normal:
                case UndoState.Redo:
                    if (UndoStack.Count > 0)
                    {
                        IUndoUnit previousUnit = UndoStack.Peek();
                        if (previousUnit.Merge(unit))
                            return;
                    }
                    UndoStack.Push(unit);
                    break;
                case UndoState.Undo:
                    if (RedoStack.Count > 0)
                    {
                        IUndoUnit previousUnit = RedoStack.Peek();
                        if (previousUnit.Merge(unit))
                            return;
                    }
                    _redoStack.Push(unit);
                    break;
            }
        }

        /// <summary>
        /// Undo all undo units until a given unit
        /// </summary>
        /// <param name="unit"></param>
        public void Undo(IUndoUnit unit)
        {
            if (State != UndoState.Normal)
                throw new InvalidOperationException();
            State = UndoState.Undo;
            IUndoUnit u = UndoStack.Pop();
            u.Do();
            while (u != unit && UndoStack.Count > 0 && unit != null)
            {
                u = UndoStack.Pop();
                u.Do();
            }
            State = UndoState.Normal;
        }

        public void Close(ICompositeUndoUnit unit)
        {
            if (!IsEnabled)
            {
                throw new InvalidOperationException();
            }
            if (unit == null)
            {
                throw new ArgumentNullException("unit");
            }
            if (OpenedUnit == null)
            {
                throw new InvalidOperationException();
            }
            if (OpenedUnit != unit)
            {
                ICompositeUndoUnit unit1 = OpenedUnit;
                while ((unit1.OpenedUnit != null) && (unit1.OpenedUnit != unit))
                {
                    unit1 = unit1.OpenedUnit;
                }
                if (unit1.OpenedUnit == null)
                {
                    throw new ArgumentException("unit");
                }
                unit1.Close();
            }
            else
            {
                if (unit.OpenedUnit != null)
                {
                    unit.Close();
                }
                if (((State != UndoState.Redo) && (State != UndoState.Undo)) && (RedoStack.Count > 0))
                {
                    RedoStack.Clear();
                }
                if (unit.IsEmpty && UndoStack.Count > 0) //\\ check this
                {
#if DEBUG
                    var u = UndoStack.Peek();
                   // Contract.Assert(u == unit);
#endif
                    //Contract.Assert(UndoStack.Peek() == unit);
                    UndoStack.Pop();
                }
                OpenedUnit = null;
            }
        }


        /// <summary>
        /// Undo the last action
        /// </summary>
        public void Undo()
        {
            Debug.WriteLine("Undo");
            Undo(null);
        }

        /// <summary>
        /// Redo the last action
        /// </summary>
        public void Redo()
        {
            Debug.WriteLine("Redo");

            Redo(null);
        }

        public void Redo(IUndoUnit unit)
        {
            if (!IsEnabled)
            {
                throw new InvalidOperationException();
            }
            if (State != UndoState.Normal)
            {
                throw new InvalidOperationException();
            }
            State = UndoState.Redo;
            IUndoUnit u = RedoStack.Pop();
            u.Do();
            while (u != unit && RedoStack.Count > 0 && unit != null)
            {
                u = RedoStack.Pop();
                u.Do();
            }
            State = UndoState.Normal;
        }
    }
}